package com.xinggq.common.utils;

import java.net.InetAddress;
import java.net.NetworkInterface;
import java.util.concurrent.ThreadLocalRandom;
import java.util.concurrent.atomic.AtomicLong;
import lombok.Getter;

/**
 * 交易号生成器
 * 基于雪花算法(Snowflake)实现，支持高并发和分布式环境
 * 生成的ID格式：64位长整型
 * - 1位符号位（固定为0）
 * - 41位时间戳（毫秒级，可使用69年）
 * - 10位机器ID（支持1024台机器）
 * - 12位序列号（每毫秒可生成4096个ID）
 * 
 * @author xinggq
 * @since 2025/1/20
 */
public class TradeNoGenerator {
    
    /** 开始时间戳 (2024-01-01 00:00:00) */
    private static final long START_TIMESTAMP = 1704067200000L;
    
    /** 机器ID位数 */
    private static final long MACHINE_ID_BITS = 10L;
    
    /** 序列号位数 */
    private static final long SEQUENCE_BITS = 12L;
    
    /** 机器ID最大值 */
    private static final long MAX_MACHINE_ID = ~(-1L << MACHINE_ID_BITS);
    
    /** 序列号最大值 */
    private static final long MAX_SEQUENCE = ~(-1L << SEQUENCE_BITS);
    
    /** 机器ID左移位数 */
    private static final long MACHINE_ID_SHIFT = SEQUENCE_BITS;
    
    /** 时间戳左移位数 */
    private static final long TIMESTAMP_SHIFT = SEQUENCE_BITS + MACHINE_ID_BITS;
    
    /** 机器ID
     * -- GETTER --
     *  获取机器ID
     *
     * @return 当前实例的机器ID
     */
    @Getter
    private final long machineId;
    
    /** 序列号 */
    private final AtomicLong sequence = new AtomicLong(0L);
    
    /** 上次生成ID的时间戳 */
    private volatile long lastTimestamp = -1L;
    
    /** 单例实例 */
    private static volatile TradeNoGenerator instance;
    
    /**
     * 私有构造函数
     * @param machineId 机器ID
     */
    private TradeNoGenerator(long machineId) {
        if (machineId > MAX_MACHINE_ID || machineId < 0) {
            throw new IllegalArgumentException(
                String.format("机器ID必须在0到%d之间", MAX_MACHINE_ID));
        }
        this.machineId = machineId;
    }
    
    /**
     * 获取单例实例
     * @return TradeNoGenerator实例
     */
    public static TradeNoGenerator getInstance() {
        if (instance == null) {
            synchronized (TradeNoGenerator.class) {
                if (instance == null) {
                    instance = new TradeNoGenerator(generateMachineId());
                }
            }
        }
        return instance;
    }
    
    /**
     * 生成下一个交易号
     * @return 唯一的交易号
     */
    public synchronized String nextTradeNo() {
        return String.valueOf(nextId());
    }
    
    /**
     * 生成下一个ID
     * @return 唯一的长整型ID
     */
    public synchronized long nextId() {
        long currentTimestamp = getCurrentTimestamp();
        
        // 时钟回拨检查
        if (currentTimestamp < lastTimestamp) {
            throw new RuntimeException(
                String.format("时钟回拨异常，拒绝生成ID。当前时间戳：%d，上次时间戳：%d", 
                    currentTimestamp, lastTimestamp));
        }
        
        // 同一毫秒内
        if (currentTimestamp == lastTimestamp) {
            long currentSequence = sequence.incrementAndGet();
            if (currentSequence > MAX_SEQUENCE) {
                // 序列号溢出，等待下一毫秒
                currentTimestamp = waitNextMillis(lastTimestamp);
                sequence.set(0L);
            }
        } else {
            // 新的毫秒，序列号重置
            sequence.set(0L);
        }
        
        lastTimestamp = currentTimestamp;
        
        // 组装ID
        return ((currentTimestamp - START_TIMESTAMP) << TIMESTAMP_SHIFT)
                | (machineId << MACHINE_ID_SHIFT)
                | sequence.get();
    }
    
    /**
     * 等待下一毫秒
     * @param lastTimestamp 上次时间戳
     * @return 新的时间戳
     */
    private long waitNextMillis(long lastTimestamp) {
        long timestamp = getCurrentTimestamp();
        while (timestamp <= lastTimestamp) {
            timestamp = getCurrentTimestamp();
        }
        return timestamp;
    }
    
    /**
     * 获取当前时间戳
     * @return 当前毫秒时间戳
     */
    private long getCurrentTimestamp() {
        return System.currentTimeMillis();
    }
    
    /**
     * 生成机器ID
     * 基于MAC地址和IP地址生成，确保分布式环境下的唯一性
     * @return 机器ID
     */
    private static long generateMachineId() {
        try {
            // 获取本机IP地址
            InetAddress ip = InetAddress.getLocalHost();
            NetworkInterface network = NetworkInterface.getByInetAddress(ip);
            
            long machineId;
            if (network == null) {
                // 无法获取网络接口，使用随机数
                machineId = ThreadLocalRandom.current().nextLong(0, MAX_MACHINE_ID + 1);
            } else {
                // 基于MAC地址生成机器ID
                byte[] mac = network.getHardwareAddress();
                if (mac != null && mac.length >= 6) {
                    machineId = ((0x000000FF & (long) mac[mac.length - 2]) 
                               | (0x0000FF00 & (((long) mac[mac.length - 1]) << 8))) >> 6;
                    machineId = machineId % (MAX_MACHINE_ID + 1);
                } else {
                    // MAC地址获取失败，使用IP地址
                    byte[] ipBytes = ip.getAddress();
                    machineId = (ipBytes[ipBytes.length - 1] & 0xFF) 
                              | ((ipBytes[ipBytes.length - 2] & 0xFF) << 8);
                    machineId = machineId % (MAX_MACHINE_ID + 1);
                }
            }
            
            return Math.abs(machineId);
        } catch (Exception e) {
            // 异常情况下使用随机机器ID
            return ThreadLocalRandom.current().nextLong(0, MAX_MACHINE_ID + 1);
        }
    }
    
    /**
     * 解析交易号信息（用于调试）
     * @param tradeNo 交易号
     * @return 解析结果
     */
    public static String parseTradeNo(String tradeNo) {
        try {
            long id = Long.parseLong(tradeNo);
            return parseId(id);
        } catch (NumberFormatException e) {
            return "无效的交易号格式";
        }
    }
    
    /**
     * 解析ID信息（用于调试）
     * @param id ID
     * @return 解析结果
     */
    public static String parseId(long id) {
        long timestamp = (id >> TIMESTAMP_SHIFT) + START_TIMESTAMP;
        long machineId = (id >> MACHINE_ID_SHIFT) & MAX_MACHINE_ID;
        long sequence = id & MAX_SEQUENCE;
        
        return String.format("时间戳: %d (%s), 机器ID: %d, 序列号: %d", 
            timestamp, new java.util.Date(timestamp), machineId, sequence);
    }

}